// The following touch screen support code by maxpautsch was merged 1/10/17
// https://github.com/maxpautsch

// Define TOUCH_CS is the user setup file to enable this code

// A demo is provided in examples Generic folder

// Additions by Bodmer to double sample, use Z value to improve detection reliability
// and to correct rotation handling

// See license in root directory.


/***************************************************************************************
** Function name:           getTouchRaw
** Description:             read raw touch position.  Always returns true.
***************************************************************************************/
uint8_t TFT_eSPI::getTouchRaw(uint16_t* x, uint16_t* y) {
    #ifdef FOURWIRETOUCH
#include "Extensions/Touch_Drivers/4WiresTouch/getRaw.h"
    *x = temp_x; *y = temp_y;
    #elif defined XPT2046TOUCH
    xpt.getXY(x, y);
    #else
#error Didn't support this Touch yet.
    #endif
    return 1;
}

/***************************************************************************************
** Function name:           getTouchRawZ
** Description:             read raw pressure on touchpad and return Z value.
***************************************************************************************/
uint16_t TFT_eSPI::getTouchRawZ(void) {
    #ifdef FOURWIRETOUCH
#include "Extensions/Touch_Drivers/4WiresTouch/getRaw.h"
    return (uint16_t)temp_z;
    #elif defined XPT2046TOUCH
    if (xpt.touchDown()) {
        return 9999;
    } else {
        return 0;
    }
    #else
#error Didn't support this Touch yet.
    #endif
}

/***************************************************************************************
** Function name:           validTouch
** Description:             read validated position. Return false if not pressed.
***************************************************************************************/
uint8_t TFT_eSPI::validTouch(uint16_t* x, uint16_t* y, uint16_t threshold) {
    uint16_t x_tmp, y_tmp;

    if (getTouchRawZ() <= threshold) {
        return false;
    }

    delay(2); // Small delay to the next sample

    getTouchRaw(&x_tmp, &y_tmp);

    *x = x_tmp;
    *y = y_tmp;

    return true;
}

/***************************************************************************************
** Function name:           getTouch
** Description:             read callibrated position. Return false if not pressed.
***************************************************************************************/
uint8_t TFT_eSPI::getTouch(uint16_t* x, uint16_t* y, uint16_t threshold) {
    uint16_t x_tmp, y_tmp;

    if (threshold < 10) {
        threshold = 10;
    }
    if (_pressTime > millis()) {
        threshold = 10;
    }

    uint8_t n = 1;
    uint8_t valid = 0;
    while (n--) {
        if (validTouch(&x_tmp, &y_tmp, threshold)) {
            valid++;
        };
    }

    if (valid < 1) {
        _pressTime = 0;
        return false;
    }

    _pressTime = millis() + 50;

    convertRawXY(&x_tmp, &y_tmp);

    if (x_tmp >= _width || y_tmp >= _height) {
        return false;
    }

    _pressX = x_tmp;
    _pressY = y_tmp;
    *x = _pressX;
    *y = _pressY;
    return valid;
}

/***************************************************************************************
** Function name:           convertRawXY
** Description:             convert raw touch x,y values to screen coordinates
***************************************************************************************/
void TFT_eSPI::convertRawXY(uint16_t* x, uint16_t* y) {
    uint16_t x_tmp = *x, y_tmp = *y, xx, yy;

    if (!touchCalibration_rotate) {
        xx = (x_tmp - touchCalibration_x0) * _width / touchCalibration_x1;
        yy = (y_tmp - touchCalibration_y0) * _height / touchCalibration_y1;
        if (touchCalibration_invert_x) {
            xx = _width - xx;
        }
        if (touchCalibration_invert_y) {
            yy = _height - yy;
        }
    } else {
        xx = (y_tmp - touchCalibration_x0) * _width / touchCalibration_x1;
        yy = (x_tmp - touchCalibration_y0) * _height / touchCalibration_y1;
        if (touchCalibration_invert_x) {
            xx = _width - xx;
        }
        if (touchCalibration_invert_y) {
            yy = _height - yy;
        }
    }
    *x = xx;
    *y = yy;
}

/***************************************************************************************
** Function name:           calibrateTouch
** Description:             generates calibration parameters for touchscreen.
***************************************************************************************/
void TFT_eSPI::calibrateTouch(uint16_t* parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size) {
    int16_t values[] = {0, 0, 0, 0, 0, 0, 0, 0};
    uint16_t x_tmp, y_tmp;



    for (uint8_t i = 0; i < 4; i++) {
        fillRect(0, 0, size + 1, size + 1, color_bg);
        fillRect(0, _height - size - 1, size + 1, size + 1, color_bg);
        fillRect(_width - size - 1, 0, size + 1, size + 1, color_bg);
        fillRect(_width - size - 1, _height - size - 1, size + 1, size + 1, color_bg);

        if (i == 5) {
            break;    // used to clear the arrows
        }

        switch (i) {
            case 0: // up left
                drawLine(0, 0, 0, size, color_fg);
                drawLine(0, 0, size, 0, color_fg);
                drawLine(0, 0, size, size, color_fg);
                break;
            case 1: // bot left
                drawLine(0, _height - size - 1, 0, _height - 1, color_fg);
                drawLine(0, _height - 1, size, _height - 1, color_fg);
                drawLine(size, _height - size - 1, 0, _height - 1, color_fg);
                break;
            case 2: // up right
                drawLine(_width - size - 1, 0, _width - 1, 0, color_fg);
                drawLine(_width - size - 1, size, _width - 1, 0, color_fg);
                drawLine(_width - 1, size, _width - 1, 0, color_fg);
                break;
            case 3: // bot right
                drawLine(_width - size - 1, _height - size - 1, _width - 1, _height - 1, color_fg);
                drawLine(_width - 1, _height - 1 - size, _width - 1, _height - 1, color_fg);
                drawLine(_width - 1 - size, _height - 1, _width - 1, _height - 1, color_fg);
                break;
        }

        // user has to get the chance to release
        if (i > 0) {
            delay(1000);
        }

        for (uint8_t j = 0; j < 8; j++) {
            // Use a lower detect threshold as corners tend to be less sensitive
            while (!validTouch(&x_tmp, &y_tmp, 10));
            values[i * 2  ] += x_tmp;
            values[i * 2 + 1] += y_tmp;
        }
        values[i * 2  ] /= 8;
        values[i * 2 + 1] /= 8;
    }


    // from case 0 to case 1, the y value changed.
    // If the measured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched.
    touchCalibration_rotate = false;
    if (abs(values[0] - values[2]) > abs(values[1] - values[3])) {
        touchCalibration_rotate = true;
        touchCalibration_x0 = (values[1] + values[3]) / 2; // calc min x
        touchCalibration_x1 = (values[5] + values[7]) / 2; // calc max x
        touchCalibration_y0 = (values[0] + values[4]) / 2; // calc min y
        touchCalibration_y1 = (values[2] + values[6]) / 2; // calc max y
    } else {
        touchCalibration_x0 = (values[0] + values[2]) / 2; // calc min x
        touchCalibration_x1 = (values[4] + values[6]) / 2; // calc max x
        touchCalibration_y0 = (values[1] + values[5]) / 2; // calc min y
        touchCalibration_y1 = (values[3] + values[7]) / 2; // calc max y
    }

    // in addition, the touch screen axis could be in the opposite direction of the TFT axis
    touchCalibration_invert_x = false;
    if (touchCalibration_x0 > touchCalibration_x1) {
        values[0] = touchCalibration_x0;
        touchCalibration_x0 = touchCalibration_x1;
        touchCalibration_x1 = values[0];
        touchCalibration_invert_x = true;
    }
    touchCalibration_invert_y = false;
    if (touchCalibration_y0 > touchCalibration_y1) {
        values[0] = touchCalibration_y0;
        touchCalibration_y0 = touchCalibration_y1;
        touchCalibration_y1 = values[0];
        touchCalibration_invert_y = true;
    }

    // pre calculate
    touchCalibration_x1 -= touchCalibration_x0;
    touchCalibration_y1 -= touchCalibration_y0;

    if (touchCalibration_x0 == 0) {
        touchCalibration_x0 = 1;
    }
    if (touchCalibration_x1 == 0) {
        touchCalibration_x1 = 1;
    }
    if (touchCalibration_y0 == 0) {
        touchCalibration_y0 = 1;
    }
    if (touchCalibration_y1 == 0) {
        touchCalibration_y1 = 1;
    }

    // export parameters, if pointer valid
    if (parameters != NULL) {
        parameters[0] = touchCalibration_x0;
        parameters[1] = touchCalibration_x1;
        parameters[2] = touchCalibration_y0;
        parameters[3] = touchCalibration_y1;
        parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x << 1) | (touchCalibration_invert_y << 2);
    }
}


/***************************************************************************************
** Function name:           setTouch
** Description:             imports calibration parameters for touchscreen.
***************************************************************************************/
void TFT_eSPI::setTouch(uint16_t* parameters) {
    touchCalibration_x0 = parameters[0];
    touchCalibration_x1 = parameters[1];
    touchCalibration_y0 = parameters[2];
    touchCalibration_y1 = parameters[3];

    if (touchCalibration_x0 == 0) {
        touchCalibration_x0 = 1;
    }
    if (touchCalibration_x1 == 0) {
        touchCalibration_x1 = 1;
    }
    if (touchCalibration_y0 == 0) {
        touchCalibration_y0 = 1;
    }
    if (touchCalibration_y1 == 0) {
        touchCalibration_y1 = 1;
    }

    touchCalibration_rotate = parameters[4] & 0x01;
    touchCalibration_invert_x = parameters[4] & 0x02;
    touchCalibration_invert_y = parameters[4] & 0x04;
}
